package com.qingzhuge.common.concurrent;

import com.qingzhuge.common.utils.DateUtil;
import com.qingzhuge.common.utils.JvmUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @author : zero.xiao
 * @date : 2018/11/14 0014 上午 11:06
 * @modified :
 * @since :
 * @description :线程池的拒绝策略，是指当任务添加到线程池中被拒绝，而采取的处理措施。
 * 当任务添加到线程池中之所以被拒绝，可能是由于：
 * 第一，线程池异常关闭。
 * 第二，任务数量超过线程池的最大限制。
 * 线程池共包括4种拒绝策略，它们分别是：AbortPolicy, CallerRunsPolicy, DiscardOldestPolicy和DiscardPolicy。
 * ThreadPoolExecutor默认的拒绝策略是AbortPolicy
 */
public class QingRejectedExecutionHandler implements RejectedExecutionHandler{
    protected static final Logger logger = LoggerFactory.getLogger(QingRejectedExecutionHandler.class);
    private final String threadName;
    private volatile long lastPrintTime = 0;
    private static Semaphore guard = new Semaphore(1);

    public QingRejectedExecutionHandler(String threadName) {
        this.threadName = threadName;
    }

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        String msg = String.format("Thread pool is exhausted!\n" +
                        " Thread Name: %s, Pool Size: %d (active: %d, core: %d, max: %d, largest: %d), Task: %d (completed: %d)\n" +
                        " Executor status:(isShutdown:%s, isTerminated:%s, isTerminating:%s)!",
                threadName, e.getPoolSize(), e.getActiveCount(), e.getCorePoolSize(), e.getMaximumPoolSize(), e.getLargestPoolSize(),
                e.getTaskCount(), e.getCompletedTaskCount(), e.isShutdown(), e.isTerminated(), e.isTerminating());
        logger.warn(msg);

        if (!e.isShutdown()) {
            e.getQueue().poll();
            e.execute(r);
        }
        dumpJStack();
        // throw new RejectedExecutionException(msg);
    }

    private void dumpJStack() {
        long now = System.currentTimeMillis();
        //dump every 10 minutes
        if (now - lastPrintTime < 10 * 60 * 1000) {
            return;
        }
        if (!guard.tryAcquire()) {
            return;
        }

        Executors.newSingleThreadExecutor(getClass().getSimpleName()).execute(() -> {
            String dateStr = DateUtil.format(new Date());
            FileOutputStream jstackStream = null;
            try {
                String dumpPath = "./logs";
                jstackStream = new FileOutputStream(new File(dumpPath, "QingTing_JStack.log" + "." + dateStr));
                JvmUtil.jstack(jstackStream);
            } catch (Throwable t) {
                logger.error("dump jstack error", t);
            } finally {
                guard.release();
                if (jstackStream != null) {
                    try {
                        jstackStream.flush();
                        jstackStream.close();
                    } catch (IOException e) {
                        logger.warn(e.getMessage());
                    }
                }
            }
            lastPrintTime = System.currentTimeMillis();
        });
    }
}
